﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System;
using Bravura.Contracts.PluginTypes;
using Bravura.Core.PluginLib;
using Bravura.Native.PluginBase;
using Bravura.Native.PluginBase.Oscillators;
using Bravura.Native.PluginBase.Waveforms;

namespace Bravura.Native.Plugins.Instruments
{
	/// <summary>
	/// Output channels : 2 
	/// Voices : 10
	/// 
	/// The outputs of four LFOs are normalized against each other, and amplitude of each oscillator
	/// is modulated with the normalized output values. This produces a sound where the amplitude of
	/// each oscillator is modulated somewhat unpredictably but the overall amplitude is constant. 
	/// </summary>
	public class Driver : Instrument<Driver.DriverVoice>, IInstrument
	{
		#region Properties

		/// <summary>
		/// True for automatic random pan per note.
		/// </summary>
		public bool AutoPan
		{
			get;
			set;
		}

		/// <summary>
		/// Minimum automatic pan value.
		/// </summary>
		public double MinPan
		{
			get;
			set;
		}

		/// <summary>
		/// Maximum automatic pan value.
		/// </summary>
		public double MaxPan
		{
			get;
			set;
		}

		/// <summary>
		/// LFO biases.
		/// </summary>
		public double[] LfoBiases
		{
			get;
			set;
		}

		/// <summary>
		/// LFO amplitudes.
		/// </summary>
		public double[] LfoAmps
		{
			get;
			set;
		}

		/// <summary>
		/// LFO frequencies.
		/// </summary>
		public double[] LfoFreqs
		{
			get;
			set;
		}

		/// <summary>
		/// Oscillator amplitude multipliers.
		/// </summary>
		public double[] OscAmps
		{
			get;
			set;
		}

		/// <summary>
		/// Oscillator frequency multipliers.
		/// </summary>
		public double[] OscFreqs
		{
			get;
			set;
		}

		#endregion

		public Driver(IAudioPluginHost host, int sampleRate) : base(host, sampleRate, 10)
		{
			this.AutoPan = true;
			this.MinPan = 0.25;
			this.MaxPan = 0.75;
			this.LfoBiases = new[] { 0.1, 0.1, 0.1, 0.1 };
			this.LfoAmps = new[] { 1.3, 1.2, 1.1, 1.0 };
			this.LfoFreqs = new[] { 0.5 + Utilities.Random.NextDouble(), 0.5 + Utilities.Random.NextDouble(), 0.5 + Utilities.Random.NextDouble(), 0.5 + Utilities.Random.NextDouble() };
			this.OscAmps = new[] { 0.8, 0.7, 0.6, 0.5 };
			this.OscFreqs = new[] { 1.01, 2.03, 4.05, 5.07 };
		}

		public override Driver.DriverVoice CreateVoice()
		{
			return new DriverVoice(this);
		}

		public class DriverVoice : Voice
		{
			#region Fields

			private Driver _driver = null;
			private Waveform _lfoWf = null;
			private Oscillator _lfOsc0 = null;
			private Oscillator _lfOsc1 = null;
			private Oscillator _lfOsc2 = null;
			private Oscillator _lfOsc3 = null;
			private Waveform _waveform = null;
			private Oscillator _oscillator0 = null;
			private Oscillator _oscillator1 = null;
			private Oscillator _oscillator2 = null;
			private Oscillator _oscillator3 = null;
			private double[] _k = new double[4];

			private double _lfoBias0 = 0.0;
			private double _lfoBias1 = 0.0;
			private double _lfoBias2 = 0.0;
			private double _lfoBias3 = 0.0;

			private double _lfoAmp0 = 1.0;
			private double _lfoAmp1 = 1.0;
			private double _lfoAmp2 = 1.0;
			private double _lfoAmp3 = 1.0;

			#endregion

			internal DriverVoice(Driver driver) : base (driver._sampleTime, 1.0 / driver.AttackTime, 1.0 / driver.ReleaseTime, driver.Distortion)
			{
				_driver = driver;

				_lfoWf = new SineWaveform(); // TODO : configurable?

				_lfOsc0 = new WaveformOscillator(_dt, _lfoWf, Utilities.Random.NextDouble());
				_lfOsc1 = new WaveformOscillator(_dt, _lfoWf, Utilities.Random.NextDouble());
				_lfOsc2 = new WaveformOscillator(_dt, _lfoWf, Utilities.Random.NextDouble());
				_lfOsc3 = new WaveformOscillator(_dt, _lfoWf, Utilities.Random.NextDouble());

				_waveform = new SineWaveform();  // TODO : configurable?

				_oscillator0 = new WaveformOscillator(_dt, _waveform, Utilities.Random.NextDouble());
				_oscillator1 = new WaveformOscillator(_dt, _waveform, Utilities.Random.NextDouble());
				_oscillator2 = new WaveformOscillator(_dt, _waveform, Utilities.Random.NextDouble());
				_oscillator3 = new WaveformOscillator(_dt, _waveform, Utilities.Random.NextDouble());

				reconfigure();
			}

			private void reconfigure()
			{
				_lfoBias0 = _driver.LfoBiases[0];
				_lfoBias1 = _driver.LfoBiases[1];
				_lfoBias2 = _driver.LfoBiases[2];
				_lfoBias3 = _driver.LfoBiases[3];

				_lfoAmp0 = _driver.LfoAmps[0];
				_lfoAmp1 = _driver.LfoAmps[1];
				_lfoAmp2 = _driver.LfoAmps[2];
				_lfoAmp3 = _driver.LfoAmps[3];

				_lfOsc0.SetAmplitude(_lfoAmp0);
				_lfOsc1.SetAmplitude(_lfoAmp1);
				_lfOsc2.SetAmplitude(_lfoAmp2);
				_lfOsc3.SetAmplitude(_lfoAmp3);

				_lfOsc0.SetFrequency(_driver.LfoFreqs[0]);
				_lfOsc1.SetFrequency(_driver.LfoFreqs[1]);
				_lfOsc2.SetFrequency(_driver.LfoFreqs[2]);
				_lfOsc3.SetFrequency(_driver.LfoFreqs[3]);

				_oscillator0.ModulateAmplitude(_driver.OscAmps[0]);
				_oscillator1.ModulateAmplitude(_driver.OscAmps[1]);
				_oscillator2.ModulateAmplitude(_driver.OscAmps[2]);
				_oscillator3.ModulateAmplitude(_driver.OscAmps[3]);

				_oscillator0.ModulateFrequency(_driver.OscFreqs[0]);
				_oscillator1.ModulateFrequency(_driver.OscFreqs[1]);
				_oscillator2.ModulateFrequency(_driver.OscFreqs[2]);
				_oscillator3.ModulateFrequency(_driver.OscFreqs[3]);
			}

			public override void NoteOn(double sound, double pitch, double velocity)
			{
				base.NoteOn(sound, pitch, velocity);

				_oscillator0.SetFrequency(_freqOrd);
				_oscillator1.SetFrequency(_freqOrd);
				_oscillator2.SetFrequency(_freqOrd);
				_oscillator3.SetFrequency(_freqOrd);

				if (_driver.AutoPan)
				{
					SetPan(_driver.MinPan + (_driver.MaxPan - _driver.MinPan) * Utilities.Random.NextDouble());
				}
			}

			public override void NoteOff()
			{
				base.NoteOff();
			}

			public override double[] Sample()
			{
				// get raw sample values
				double s0 = _oscillator0.Sample();
				double s1 = _oscillator1.Sample();
				double s2 = _oscillator2.Sample();
				double s3 = _oscillator3.Sample();

				// get lfo values
				_k[0] = _lfOsc0.Sample() + _lfoAmp0 + _lfoBias0;
				_k[1] = _lfOsc1.Sample() + _lfoAmp1 + _lfoBias1;
				_k[2] = _lfOsc2.Sample() + _lfoAmp2 + _lfoBias2;
				_k[3] = _lfOsc3.Sample() + _lfoAmp3 + _lfoBias3;

				// normalize lfo values
				Utilities.Normalize(_k);

				// modulate amplitudes of samples
				s0 = s0 * _k[0];
				s1 = s1 * _k[1];
				s2 = s2 * _k[2];
				s3 = s3 * _k[3];

				// get summed sample
				double ss = s0 + s1 + s2 + s3;

				// apply distortion ?
				if (_dist != 1.0)
				{
					ss = (ss < 0.0) ? -Math.Pow(-ss, _dist) : Math.Pow(ss, _dist);
				}

				// apply level
				ss *= _velocity * _level * _level;

				// update voice envelope
				UpdateEnvelope();

				// load sample
				_sample[0] = _mix0 * ss;
				_sample[1] = _mix1 * ss;

				// return sample
				return _sample;
			}
		}
	}
}
